package com.pearl.security.multiloginreject.demo.session.redis;

import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executor;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.FlushMode;
import org.springframework.session.IndexResolver;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.web.http.AbstractRedisHttpSessionConfiguration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;

/*@Configuration(
        proxyBeanMethods = false
)*/
public class MyRedisIndexedHttpSessionConfiguration extends AbstractRedisHttpSessionConfiguration<MyRedisIndexedSessionRepository> implements EmbeddedValueResolverAware, ImportAware {
    private String cleanupCron = "0 * * * * *";
    private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
    private IndexResolver<Session> indexResolver;
    private ApplicationEventPublisher applicationEventPublisher;
    private Executor redisTaskExecutor;
    private Executor redisSubscriptionExecutor;
    private StringValueResolver embeddedValueResolver;
    private ClassLoader classLoader;

    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Bean
    public MyRedisIndexedSessionRepository sessionRepository() {

        RedisTemplate<String, Object> redisTemplate = this.createRedisTemplate();
        MyRedisIndexedSessionRepository sessionRepository = new MyRedisIndexedSessionRepository(redisTemplate);

        sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
        if (this.indexResolver != null) {
            sessionRepository.setIndexResolver(this.indexResolver);
        }

        if (this.getDefaultRedisSerializer() != null) {
            sessionRepository.setDefaultSerializer(this.getDefaultRedisSerializer());
        }

        sessionRepository.setDefaultMaxInactiveInterval(this.getMaxInactiveInterval());
        if (StringUtils.hasText(this.getRedisNamespace())) {
            sessionRepository.setRedisKeyNamespace(this.getRedisNamespace());
        }

        sessionRepository.setFlushMode(this.getFlushMode());
        sessionRepository.setSaveMode(this.getSaveMode());
        sessionRepository.setCleanupCron(this.cleanupCron);
        int database = this.resolveDatabase();
        sessionRepository.setDatabase(database);
/*        this.getSessionRepositoryCustomizers().forEach((sessionRepositoryCustomizer) -> {
            sessionRepositoryCustomizer.customize(sessionRepository);
        });*/

        return sessionRepository;
    }

    protected RedisTemplate<String, Object> createRedisTemplate() {
/*        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        ObjectMapper om = new ObjectMapper();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(om,Object.class);
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key 序列化
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        // value 序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);*/
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        if (this.getDefaultRedisSerializer() != null) {
            redisTemplate.setDefaultSerializer(this.getDefaultRedisSerializer());
        }
        redisTemplate.setConnectionFactory(this.getRedisConnectionFactory());
        redisTemplate.setBeanClassLoader(this.classLoader);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public RedisMessageListenerContainer springSessionRedisMessageListenerContainer(MyRedisIndexedSessionRepository sessionRepository) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(this.getRedisConnectionFactory());
        if (this.redisTaskExecutor != null) {
            container.setTaskExecutor(this.redisTaskExecutor);
        }

        if (this.redisSubscriptionExecutor != null) {
            container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
        }

        container.addMessageListener(sessionRepository, Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()), new ChannelTopic(sessionRepository.getSessionExpiredChannel())));
        container.addMessageListener(sessionRepository, Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*")));
        return container;
    }

    @Bean
    public InitializingBean enableRedisKeyspaceNotificationsInitializer() {
        return new EnableRedisKeyspaceNotificationsInitializer(this.getRedisConnectionFactory(), this.configureRedisAction);
    }

    public void setCleanupCron(String cleanupCron) {
        this.cleanupCron = cleanupCron;
    }

    @Autowired(
            required = false
    )
    public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) {
        this.configureRedisAction = configureRedisAction;
    }

    @Autowired
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Autowired(
            required = false
    )
    public void setIndexResolver(IndexResolver<Session> indexResolver) {
        this.indexResolver = indexResolver;
    }

    @Autowired(
            required = false
    )
    @Qualifier("springSessionRedisTaskExecutor")
    public void setRedisTaskExecutor(Executor redisTaskExecutor) {
        this.redisTaskExecutor = redisTaskExecutor;
    }

    @Autowired(
            required = false
    )
    @Qualifier("springSessionRedisSubscriptionExecutor")
    public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) {
        this.redisSubscriptionExecutor = redisSubscriptionExecutor;
    }

    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.embeddedValueResolver = resolver;
    }

    public void setImportMetadata(AnnotationMetadata importMetadata) {
        Map<String, Object> attributeMap = importMetadata.getAnnotationAttributes(EnableRedisIndexedHttpSession.class.getName());
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
        if (attributes != null) {
            this.setMaxInactiveInterval(Duration.ofSeconds((long) (Integer) attributes.getNumber("maxInactiveIntervalInSeconds")));
            String redisNamespaceValue = attributes.getString("redisNamespace");
            if (StringUtils.hasText(redisNamespaceValue)) {
                this.setRedisNamespace(this.embeddedValueResolver.resolveStringValue(redisNamespaceValue));
            }

            this.setFlushMode((FlushMode) attributes.getEnum("flushMode"));
            this.setSaveMode((SaveMode) attributes.getEnum("saveMode"));
            String cleanupCron = attributes.getString("cleanupCron");
            if (StringUtils.hasText(cleanupCron)) {
                this.setCleanupCron(cleanupCron);
            }

        }
    }

    private int resolveDatabase() {
        if (ClassUtils.isPresent("io.lettuce.core.RedisClient", (ClassLoader) null) && this.getRedisConnectionFactory() instanceof LettuceConnectionFactory) {
            return ((LettuceConnectionFactory) this.getRedisConnectionFactory()).getDatabase();
        } else {
            return ClassUtils.isPresent("redis.clients.jedis.Jedis", (ClassLoader) null) && this.getRedisConnectionFactory() instanceof JedisConnectionFactory ? ((JedisConnectionFactory) this.getRedisConnectionFactory()).getDatabase() : 0;
        }
    }

    static class EnableRedisKeyspaceNotificationsInitializer implements InitializingBean {
        private final RedisConnectionFactory connectionFactory;
        private final ConfigureRedisAction configure;

        EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory, ConfigureRedisAction configure) {
            this.connectionFactory = connectionFactory;
            this.configure = configure;
        }

        public void afterPropertiesSet() {
            if (this.configure != ConfigureRedisAction.NO_OP) {
                RedisConnection connection = this.connectionFactory.getConnection();

                try {
                    this.configure.configure(connection);
                } finally {
                    try {
                        connection.close();
                    } catch (Exception var8) {
                        LogFactory.getLog(this.getClass()).error("Error closing RedisConnection", var8);
                    }

                }

            }
        }
    }
}

